/* 3-dim float vector.

   Copyright (C) 2001, 2002, 2003 Stefan Maierhofer.

   Written by Stefan Maierhofer <sm@cg.tuwien.ac.at>

   This file is part of Vecmath.

   Vecmath is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   Vecmath is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Vecmath; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */

using System;
using NUnit.Framework;

namespace Vecmath
{

    /// <summary>
    /// Vec3F.
    /// </summary>
    public struct Vec3F
    {
        public static Vec3F Identity = new Vec3F(0);
        public static Vec3F UnitX = new Vec3F(1,0,0);
        public static Vec3F UnitY = new Vec3F(0,1,0);
        public static Vec3F UnitZ = new Vec3F(0,0,1);

        public Vec3F(float xyz)
        {
            _x = new float[3];
            _x[0] = xyz; _x[1] = xyz; _x[2] = xyz;
        }

        public Vec3F(float x, float y, float z)
        {
            _x = new float[3];
            _x[0] = x; _x[1] = y; _x[2] = z;
        }

        public Vec3F(ref float [] x)
        {
            _x = new float[3];
            _x[0] = x[0]; _x[1] = x[1]; _x[2] = x[2];
        }

        public float this [int index]
        {
            get { return _x[index]; }
            set { _x[index] = value; }
        }

        public float x
        {
            get { return _x[0]; }
            set { _x[0] = value; }
        }

        public float y
        {
            get { return _x[1]; }
            set { _x[1] = value; }
        }

        public float z
        {
            get { return _x[2]; }
            set { _x[2] = value; }
        }

        public static explicit operator Vec3F(Vec3D v)
        {
            return new Vec3F((float)v.x, (float)v.y, (float)v.z);
        }

        public static implicit operator Vec3F(Pnt3F v)
        {
            return new Pnt3F(v.x, v.y, v.z);
        }

        public static Vec3F operator+(Vec3F v, float f)
        {
            return new Vec3F(v[0] + f, v[1] + f, v[2] + f);
        }

        public static Vec3F operator+(Vec3F v, Vec3F w)
        {
            return new Vec3F(v[0] + w[0], v[1] + w[1], v[2] + w[2]);
        }

        public static Vec3F operator-(Vec3F v, float f)
        {
            return new Vec3F(v[0] - f, v[1] - f, v[2] - f);
        }

        public static Vec3F operator-(Vec3F v, Vec3F w)
        {
            return new Vec3F(v[0] - w[0], v[1] - w[1], v[2] - w[2]);
        }

        public static Vec3F operator-(Vec3F v)
        {
            return new Vec3F(-v[0], -v[1], -v[2]);
        }

        public static Vec3F operator*(Vec3F v, float f)
        {
            return new Vec3F(v[0] * f, v[1] * f, v[2] * f);
        }

        public static Vec3F operator/(Vec3F v, float f)
        {
            return new Vec3F(v[0] / f, v[1] / f, v[2] / f);
        }

        public static float Dot(Vec3F u, Vec3F v)
        {
            return u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
        }

        public static Vec3F Cross(Vec3F a, Vec3F b)
        {
            return new Vec3F(
                a.y * b.z - a.z * b.y,
                a.z * b.x - a.x * b.z,
                a.x * b.y - a.y * b.x
                );
        }

        public void Normalize()
        {
            this /= Length;
        }

        public Vec3F Normalized
        {
            get { return this / Length; }
        }

        public float SquaredLength
        {
            get { return _x[0] * _x[0] + _x[1] * _x[1] + _x[2] * _x[2]; }
        }

        public float Length
        {
            get { return (float)Math.Sqrt(SquaredLength); }
        }

        public int MinDim
        {
            get
            {
                return (_x[0] < _x[1])
                    ? ((_x[0] < _x[2]) ? 0 : 2)
                    : ((_x[1] < _x[2]) ? 1 : 2);
            }
        }

        public int MaxDim
        {
            get
            {
                return (_x[0] > _x[1])
                    ? ((_x[0] > _x[2]) ? 0 : 2)
                    : ((_x[1] > _x[2]) ? 1 : 2);
            }
        }

        public float Minimum
        {
            get
            {
                return (_x[0] < _x[1])
                    ? ((_x[0] < _x[2]) ? _x[0]
                    : _x[2]) : ((_x[1] < _x[2]) ? _x[1] : _x[2]);
            }
        }

        public float Maximum
        {
            get
            {
                return (_x[0] > _x[1])
                    ? ((_x[0] > _x[2]) ? _x[0]
                    : _x[2]) : ((_x[1] > _x[2]) ? _x[1] : _x[2]);
            }
        }

        public static bool operator<(Vec3F a, Vec3F b)
        {
            return (
                a._x[0] < b._x[0] &&
                a._x[1] < b._x[1] &&
                a._x[2] < b._x[2]
                );
        }

        public static bool operator<=(Vec3F a, Vec3F b)
        {
            return (
                a._x[0] <= b._x[0] &&
                a._x[1] <= b._x[1] &&
                a._x[2] <= b._x[2]
                );
        }

        public static bool operator==(Vec3F a, Vec3F b)
        {
            return (
                a._x[0] == b._x[0] &&
                a._x[1] == b._x[1] &&
                a._x[2] == b._x[2]
                );
        }

        public static bool operator!=(Vec3F a, Vec3F b)
        {
            return (
                a._x[0] != b._x[0] ||
                a._x[1] != b._x[1] ||
                a._x[2] != b._x[2]
                );
        }

        public static bool operator>=(Vec3F a, Vec3F b)
        {
            return (
                a._x[0] >= b._x[0] &&
                a._x[1] >= b._x[1] &&
                a._x[2] >= b._x[2]
                );
        }

        public static bool operator>(Vec3F a, Vec3F b)
        {
            return (
                a._x[0] > b._x[0] &&
                a._x[1] > b._x[1] &&
                a._x[2] > b._x[2]
                );
        }

        public static Vec3F Min(Vec3F a, Vec3F b)
        {
            return new Vec3F(
                Math.Min(a._x[0], b._x[0]),
                Math.Min(a._x[1], b._x[1]),
                Math.Min(a._x[2], b._x[2])
                );
        }

        public static Vec3F Max(Vec3F a, Vec3F b)
        {
            return new Vec3F(
                Math.Max(a._x[0], b._x[0]),
                Math.Max(a._x[1], b._x[1]),
                Math.Max(a._x[2], b._x[2])
                );
        }

        internal float [] _x;

        public override bool Equals(object obj)
        {
            Vec3F x = (Vec3F)obj;
            return (
                _x[0] == x._x[0] &&
                _x[1] == x._x[1] &&
                _x[2] == x._x[2]
                );
        }

        public override int GetHashCode()
        {
            return base.GetHashCode ();
        }

        public override string ToString()
        {
            return "Vec3F(" + _x[0] + ", " + _x[1] + ", " + _x[2] + ")";
        }

        static Vec3F()
        {
            System.Threading.Thread.CurrentThread.CurrentCulture =
                System.Globalization.CultureInfo.InvariantCulture;
        }
    }
    

    [TestFixture]
    public class Vec3FTest
    {
        [Test]
        public void Initialization()
        {
            Vec3F v;

            v = Vec3F.Identity;
            Assert.IsTrue(
                v._x[0] == 0 && v._x[1] == 0 && v._x[2] == 0
                );

            v = Vec3F.UnitX;
            Assert.IsTrue(
                v._x[0] == 1 && v._x[1] == 0 && v._x[2] == 0
                );

            v = Vec3F.UnitY;
            Assert.IsTrue(
                v._x[0] == 0 && v._x[1] == 1 && v._x[2] == 0
                );

            v = Vec3F.UnitZ;
            Assert.IsTrue(
                v._x[0] == 0 && v._x[1] == 0 && v._x[2] == 1
                );

            v = new Vec3F(3.14f);
            Assert.IsTrue(
                v._x[0] == 3.14f && v._x[1] == 3.14f && v._x[2] == 3.14f
                );
            
            v = new Vec3F(0.1f, 0.2f, 0.3f);
            Assert.IsTrue(
                v._x[0] == 0.1f && v._x[1] == 0.2f && v._x[2] == 0.3f
                );

            float [] fa = { 0.1f, 0.2f, 0.3f };
            v = new Vec3F(ref fa);
            Assert.IsTrue(
                v._x[0] == 0.1f && v._x[1] == 0.2f && v._x[2] == 0.3f
                );
        }

        [Test]
        public void Accessors()
        {
            Vec3F v = new Vec3F(0.1f, 0.2f, 0.3f);
            Assert.IsTrue(
                v._x[0] == v[0] && v._x[1] == v[1] && v._x[2] == v[2]
                );
            Assert.IsTrue(
                v._x[0] == v.x && v._x[1] == v.y && v._x[2] == v.z
                );
        }

        [Test]
        public void ScalarOperators()
        {
            Vec3F v = new Vec3F(0.1f, 0.2f, 0.3f);

            v += 1.0f;
            Assert.IsTrue(Vecmath.ApproxEquals(v, new Vec3F(1.1f, 1.2f, 1.3f)));

            v -= 1.0f;
            Assert.IsTrue(Vecmath.ApproxEquals(v, new Vec3F(0.1f, 0.2f, 0.3f)));
            
            v *= 2.0f;
            Assert.IsTrue(Vecmath.ApproxEquals(v, new Vec3F(0.2f, 0.4f, 0.6f)));

            v /= 2.0f;
            Assert.IsTrue(Vecmath.ApproxEquals(v, new Vec3F(0.1f, 0.2f, 0.3f)));
        }

        [Test]
        public void VecOperators()
        {
            Vec3F v;

            v = new Vec3F(1,2,3) + new Vec3F(3,2,1);
            Assert.IsTrue(Vecmath.ApproxEquals(v, new Vec3F(4, 4, 4)));

            v = new Vec3F(1,2,3) - new Vec3F(3,2,1);
            Assert.IsTrue(Vecmath.ApproxEquals(v, new Vec3F(-2, 0, 2)));
        }

        [Test]
        public void Length()
        {
            Vec3F v = new Vec3F(0.1f, 0.2f, 0.3f);
            Assert.IsTrue(Vecmath.ApproxEquals(v.Length * v.Length, v.SquaredLength));
        }

        [Test]
        public void RelationalOperators()
        {
            Assert.IsTrue(new Vec3F(2,3,-1) < new Vec3F(3,10,0));
            Assert.IsTrue(new Vec3F(2,3,-1) <= new Vec3F(3,10,0));
            Assert.IsTrue(new Vec3F(2,3,-1) != new Vec3F(3,10,0));
            Assert.IsFalse(new Vec3F(2,3,-1) == new Vec3F(3,10,0));
            Assert.IsFalse(new Vec3F(2,3,-1) >= new Vec3F(3,10,0));
            Assert.IsFalse(new Vec3F(2,3,-1) > new Vec3F(3,10,0));

            Assert.IsFalse(new Vec3F(2,3,-1) < new Vec3F(1,0,-5));
            Assert.IsFalse(new Vec3F(2,3,-1) <= new Vec3F(1,0,-5));
            Assert.IsTrue(new Vec3F(2,3,-1) != new Vec3F(1,0,-5));
            Assert.IsFalse(new Vec3F(2,3,-1) == new Vec3F(1,0,-5));
            Assert.IsTrue(new Vec3F(2,3,-1) >= new Vec3F(1,0,-5));
            Assert.IsTrue(new Vec3F(2,3,-1) > new Vec3F(1,0,-5));

            Assert.IsFalse(new Vec3F(3,4,5) < new Vec3F(3,4,5));
            Assert.IsTrue(new Vec3F(3,4,5) <= new Vec3F(3,4,5));
            Assert.IsFalse(new Vec3F(3,4,5) != new Vec3F(3,4,5));
            Assert.IsTrue(new Vec3F(3,4,5) == new Vec3F(3,4,5));
            Assert.IsTrue(new Vec3F(3,4,5) >= new Vec3F(3,4,5));
            Assert.IsFalse(new Vec3F(3,4,5) > new Vec3F(3,4,5));

            Assert.IsFalse(new Vec3F(1,2,3) < new Vec3F(0,1,3));
            Assert.IsFalse(new Vec3F(1,2,3) <= new Vec3F(0,1,3));
            Assert.IsTrue(new Vec3F(1,2,3) != new Vec3F(0,1,3));
            Assert.IsFalse(new Vec3F(1,2,3) == new Vec3F(0,1,3));
            Assert.IsTrue(new Vec3F(1,2,3) >= new Vec3F(0,1,3));
            Assert.IsFalse(new Vec3F(1,2,3) > new Vec3F(0,1,3));

            Assert.IsFalse(new Vec3F(1,2,3) < new Vec3F(0,1,4));
            Assert.IsFalse(new Vec3F(1,2,3) <= new Vec3F(0,1,4));
            Assert.IsTrue(new Vec3F(1,2,3) != new Vec3F(0,1,4));
            Assert.IsFalse(new Vec3F(1,2,3) == new Vec3F(0,1,4));
            Assert.IsFalse(new Vec3F(1,2,3) >= new Vec3F(0,1,4));
            Assert.IsFalse(new Vec3F(1,2,3) > new Vec3F(0,1,4));
        }

        [Test]
        public void MinMax()
        {
            Vec3F v = new Vec3F(1,2,3);
            Assert.IsTrue(v.MinDim == 0);
            Assert.IsTrue(v.MaxDim == 2);
            Assert.IsTrue(v.Minimum == 1);
            Assert.IsTrue(v.Maximum == 3);
            Assert.IsTrue(Vec3F.Min(new Vec3F(1,2,3), new Vec3F(3,2,1)) == new Vec3F(1,2,1));
            Assert.IsTrue(Vec3F.Max(new Vec3F(1,2,3), new Vec3F(3,2,1)) == new Vec3F(3,2,3));
        }

        [Test]
        public void VecOps()
        {
            Vec3F u = new Vec3F(1,2,3);
            Vec3F v = new Vec3F(4,5,6);

            Assert.IsTrue(Vecmath.ApproxEquals(Vec3F.Dot(u,v), 32));
            Assert.IsTrue(Vecmath.ApproxEquals(Vec3F.Cross(u,v), new Vec3F(-3, 6, -3)));
            Assert.IsTrue(Vecmath.ApproxEquals(u.Normalized.Length, 1));

            v.Normalize();
            Assert.IsTrue(Vecmath.ApproxEquals(v.Length, 1));
        }

        [Test]
        public void ToStringTest()
        {
            Vec3F v = new Vec3F(0.1f, 0.2f, 0.3f);
            Assert.AreEqual("Vec3F(0.1, 0.2, 0.3)", v.ToString());
        }
    }

}
